#ifndef _MESH_VIEW
#define _MESH_VIEW
#include <deque>
#include <set>

#include "para.h"

namespace math { namespace ui {
class MeshView  : public GLView 
{
public:
	typedef float value_type ;
	typedef math::vector<value_type, 2> vector2 ;
	typedef math::vector<value_type, 3> vector3 ;
	typedef math::vector<value_type, 2> point2 ;
	typedef math::vector<value_type, 3> point3 ;

	typedef math::HierarchyMesh<math::hierarchy_mesh_traits<value_type, 3> >  mesh_type ;
	typedef math::PseudoMesh<value_type , 3> pseudomesh_type ;
	typedef math::hierarchy_mesh_traits <value_type, 3>::FaceType face_type ;
	typedef math::hierarchy_mesh_traits <value_type, 3>::VertexType vertex_type ;
	typedef math::hierarchy_mesh_traits <value_type, 3>::HalfEdgeType halfedge_type ;
	typedef math::hierarchy_mesh_traits <value_type, 3>::EdgeType edge_type ;

	typedef math::vector<value_type, 4> frgba_type ;
	// paint mode : paint mesh colors. 
	// edit mode : edit the topology of the model (resolution or hard edges)

	MeshView (Model * model, unsigned ID) : mp_brush(NULL), center_face(NULL), GLView(model, ID) 
	{
		this->mp_model = model ; this->mp_brush = mp_model->get_brush();
		m_update_whole_model = true; 
	}
	~ MeshView () {}
	virtual void set_shader (Shader * shader) {this->mp_shader = shader ;}
	virtual void set_viewmode (DISPLAY_MODE vm = FILL_MODE)
	{
		mainView.display_mode = vm ;
	}

	virtual void setMousePos (int _x, int _y)
	{
		mainView.mouse_winpos = _x, _y; 
		this->mp_brush->set_win_pos (vector2 (_x, _y)); 
	}

	virtual void setLButtonDown (bool flag = true)
	{
		this->mainView.mouseLeftDown = flag;
	}
	virtual void setRButtonDown (bool flag = true)
	{
		this->mainView.mouseRightDown = flag;
	}

	virtual void setMButtonDown (bool flag = true)
	{
		this->mainView.mouseMiddleDown = flag;
	}
	virtual void rotateCamera (int x, int y)
	{
//		if(m_mouseLeftDown)
//		{
		mainView.cameraAglY += (x - mainView.mouse_winpos.x);
		mainView.cameraAglX += (y - mainView.mouse_winpos.y);

		mainView.mouse_winpos = x, y; 

//		}
	}
	virtual void translateCamera (int x, int y)
	{
		mainView.focus.x += 0.005 * (x - mainView.mouse_winpos.x);
		mainView.focus.y -= 0.005 * (y - mainView.mouse_winpos.y);

		mainView.mouse_winpos = x, y;
	}
	virtual void zoomCamera(int x, int y)
	{
		mainView.focus.z -= (y - mainView.mouse_winpos.y) * 0.01f;
		mainView.mouse_winpos.y = y;

	}

	void resize (int w, int h)
	{
		this->mp_brush->set_winsize (this->m_width, this->m_height) ;
	}

	inline void enable_shader () const {mp_shader->set_uniform("draw_face", 1); }
	inline void disable_shader () const {mp_shader->set_uniform("draw_face", 0); }

	virtual void paint () 
	{
		update_brush_size(); 
		update_color ();


		// scissor the region
		set_scissor(); 
		// Set the left view
		setViewport(this->m_xpos, this->m_ypos, this->m_width, this->m_height, mainView.projectionNear, mainView.projectionFar);
		setViewMode(mainView.display_mode);

		glPushMatrix();
		glTranslatef(mainView.focus.x, mainView.focus.y, mainView.focus.z);
		glRotatef(mainView.cameraAglX, 1, 0, 0); // pitch
		glRotatef(mainView.cameraAglY, 0, 1, 0); // heading

		//glClearColor(0.0f, 0.0f, 0.0f, 1);
		color_type & c = this->mainView.backgroundColor  ;
		glClearColor(c[0], c[1], c[2], c[3]);

		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
		draw_grid(10, 1);
		//	glColor3f(1.0, 1.0, 1.0);
		mp_brush->getObjCoordEnv(); 
		sence();
		glPopMatrix(); 

		glPushMatrix();
		glLoadIdentity();
		glTranslatef(0, 0, -0.01);
		mp_brush->getBrushCoordEnv(); 

		mp_brush->draw(); 

		glPopMatrix(); 

		bfs_culling();
		paramaterization () ;
		glDisable(GL_LIGHTING);
	}

	typedef Segment<value_type, 3> segment_type ;
	face_type * pick_face ()
	{
		point3 p1, p2;
		mp_brush->pick_ray(p1, p2);

		std::vector <unsigned > v_face_id ;
		mp_model->get_mesh()->pick(segment_type (p1, p2), v_face_id) ;
		if (v_face_id.size() > 0) 
		{
			face_type * face = mp_model->get_mesh()->faces[v_face_id[0]] ;
			//face->set_selected(! face->is_selected());
			return face ;
		}
		return NULL ;
	}
	halfedge_type * pick_edge ()
	{

		face_type * face = pick_face(); 

		if (face == NULL) return NULL;
		std::vector<point3 *> vcoord;
		point3 win_coord[3];
		face->Face2VertexCoord(vcoord);
		for (int i = 0; i < 3; ++i ) 
		{
			win_coord [i] = mp_brush->objVM.mappingObj2Win(* vcoord[i]);
			win_coord [i].z = 0.0f;
		}
		point2 pos = mp_brush->get_win_pos() ; 
		point3 mouse_pos(pos.x, pos.y, 0.0f);

		short tag = -1; 
		float distance = INFINITY ;

		for (int i = 0; i < 3; ++i)
		{
			vector3 p0 = win_coord[i] - win_coord[(i+1)%3];
			p0 = p0.normalize(); 

			vector3 p1 = mouse_pos - win_coord[(i+1)%3] ;

			vector3 dis_v = p0^p1; 
			if (dis_v.module2() < distance * distance)
			{
				tag = i ;
				distance = dis_v.module();
			}
		}
		
		// Update halfedge to this face
		//face->strongEdge[tag] = ! face->strongEdge[tag]; 
		face->flip_strong_edge(tag); 
		
		// Update halfedge to the opposite face
		halfedge_type * he = face->get_he_by_counter(tag); 
		he = he->flip ;
		face_type * opp_face = (face_type *)he->face; 
		opp_face->set_strong_edge(opp_face->get_halfedge_counter (he), face->get_strong_edge (tag) ) ;

		/*
		he->face->set_strong_edge()
		he->face->strongEdge[he->face->halfEdgeCount(he)] = face->strongEdge[tag];
		*/
		
	}
	void paint_model ()
	{
		mp_brush->paint (mp_model->get_pseudomesh());
	}
	void select_edge ()
	{
		pick_edge(); 
	}
	void select_face () 
	{
		face_type * face = pick_face() ;
		if (NULL == face) return ;
		face->set_selected(! face->is_selected());
		if (face->is_selected())
		{
			this->m_selected_faces.insert(face);
		}
		else
		{
			this->m_selected_faces.erase(face);
		}
	}
	void draw_mesh_facet (mesh_type * mesh)
	{

		for (unsigned i = 0; i < mesh->faces.size(); ++i)
		{
			if (mesh->faces[i]->is_selected()) mesh->faces[i]->draw_blend(); 
		}

		enable_shader();
		for (unsigned i = 0; i < mesh->faces.size(); ++i)
		{
			mesh->faces[i]->draw_smooth(); 
			//mesh->faces[i]->draw_flat(); 

		}		
		disable_shader();
		for (unsigned i = 0; i < mesh->faces.size(); ++i)
		{
			mesh->faces[i]->draw_strong_edge(); 

		}
	}

	void set_update_whole_model ()
	{
		m_update_whole_model = true ;
	}
	void update_whole_model (mesh_type * mesh)
	{
		for (unsigned i = 0; i < mesh->faces.size(); ++i)
		{
			mesh->faces[i]->paint_face_texture(); 
		}
		m_update_whole_model = false; 
	}

	void sence () 
	{	
		glEnable(GL_LIGHTING);
		glEnable(GL_TEXTURE_2D);
		glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
		mesh_type * pmesh = mp_model->get_mesh (); 
		if (true == m_update_whole_model)
			update_whole_model(pmesh); 
		else
			draw_mesh_facet(pmesh);
		glFlush();		
		glDisable(GL_TEXTURE_2D);
		glDisable(GL_LIGHTING);
	}

	void draw_grid(float size, float step)
	{
		// disable lighting
		glDisable(GL_LIGHTING);
		glBegin(GL_LINES);
		glColor3f(0.3f, 0.3f, 0.3f);
		float offset = 0.0f;
		for(float i=step; i <= size; i+= step)
		{
			glVertex3f(-size, offset,  i);   // lines parallel to X-axis
			glVertex3f( size, offset,  i);
			glVertex3f(-size, offset, -i);   // lines parallel to X-axis
			glVertex3f( size, offset, -i);

			glVertex3f( i, offset, -size);   // lines parallel to Z-axis
			glVertex3f( i, offset,  size);
			glVertex3f(-i, offset, -size);   // lines parallel to Z-axis
			glVertex3f(-i, offset,  size);
		}

		// x-axis
		glColor3f(0.5f, offset, 0);
		glVertex3f(-size, offset, 0);
		glVertex3f( size, offset, 0);

		// z-axis
		glColor3f(0,offset,0.5f);
		glVertex3f(0, offset, -size);
		glVertex3f(0, offset,  size);

		glEnd();
		glEnable(GL_LIGHTING);
		glFlush();
	}

	void up_sampling() 
	{
		for (std::set<face_type * >::iterator iter = this->m_selected_faces.begin();  
			 iter != this->m_selected_faces.end(); ++iter )
		{
			(*iter)->up_sampling();
		}
		update_sampling();
	}

	void down_sampling()
	{
		for (std::set<face_type * >::iterator iter = this->m_selected_faces.begin();  
			 iter != this->m_selected_faces.end(); ++iter )
		{
			(*iter)->down_sampling();
		}
		update_sampling();
	}
protected:
	void update_sampling ()
	{
		for (std::set<face_type * >::iterator iter = this->m_selected_faces.begin();  
			 iter != this->m_selected_faces.end(); ++iter )
		{
			(*iter)->paint_face_texture(); 
		} 
	}
protected:
	void update_brush_style () 
	{
		
	}
	void update_brush_size()
	{
		Slider * slider = (Slider *) mp_model->query_control(BRUSH_SIZE_SLIDER);
//		std::cout<<slider->get_position()<<std::endl;
		mp_brush->set_pixel_size(unsigned (50 * slider->get_position())); 
	}

	void update_color ()
	{
		frgba_type color (1.0, 1.0, 1.0, 1.0);
		Slider * slider = (Slider *) mp_model->query_control(COLOR_RED_SLIDER);
		color.r *= slider->get_position(); 
		slider = (Slider *) mp_model->query_control(COLOR_GREEN_SLIDER);
		color.g *= slider->get_position(); 
		slider = (Slider *) mp_model->query_control(COLOR_BLUE_SLIDER);
		color.b *= slider->get_position(); 		
		slider = (Slider *) mp_model->query_control(COLOR_ALPHA_SLIDER);
		color.a *= slider->get_position(); 		
		
		mp_brush->set_color (color); 
	}
	void paramaterization ()
	{
		if (center_face == NULL) return ;
		pseudomesh_type * pseudomesh = mp_model->get_pseudomesh();
		if (pseudomesh->faces.size() )
		{
			LSCM lscm (* pseudomesh ) ;
			lscm.apply();
		}
		vector2 trans;
		float scale, rotate_cos, rotate_sin ;
		OPAffineTransformation::compute_affine (center_face, this->mp_brush,  trans, scale, rotate_cos, rotate_sin) ;
		OPAffineTransformation affine(pseudomesh, trans, scale, rotate_cos, rotate_sin) ;
		pseudomesh = affine ;
	}

	// Test if the triangle is covered by brush. 
	bool is_face_visible (face_type * face)
	{
		std::vector<point3 * > v_coord ;
		face->Face2VertexCoord(v_coord); 
		return this->mp_brush->isVisible(v_coord);
	} 
	bool is_face_covered(face_type * face )
	{
		/*
		std::vector<point3 * > v_coord ;
		face->Face2VertexCoord(v_coord); 
		for (unsigned i = 0; i < v_coord.size(); ++i)
		{
			if (this->mp_brush->is_in_brush( *v_coord[i]) ) return true; 
		}
		return false; 
		*/
		return this->mp_brush->is_intersect_brush(face); 
	}
	void bfs_culling () 
	{
		for (unsigned i = 0; i < v_faces_tagged.size() ; ++i) 
		{
			v_faces_tagged[i]->set_tagged(false ); 
		}
		pseudomesh_type * pseudo_mesh = mp_model->get_pseudomesh(); 
		pseudo_mesh->faces.clear(); 

		this->v_faces_tagged.clear(); 
		center_face = pick_face() ;
		if (center_face == NULL) return ; 

		// BFS CULLING 
		std::deque <face_type * > faceDeque ;
		faceDeque.push_back(center_face);
		center_face->set_tagged(true); 

		while (faceDeque.size() > 0)
		{
			face_type * front = faceDeque.front();
			pseudo_mesh->faces.push_back(front); 
			faceDeque.pop_front(); 
			v_faces_tagged.push_back(front);

			halfedge_type * he_iter = (halfedge_type * )front->he ;
			halfedge_type * he_end = he_iter ;
			do 
			{
				vertex_type * v = (vertex_type * )he_iter->from_v ;
				vertex_type::VFIter vfiter = v->vfbegin_safe();
				vfiter = v->vfbegin_safe();
				do 
				{
					face_type * face = (face_type *)(*vfiter);
					if (false == face->get_tagged() && is_face_covered(face) )
					{
						faceDeque.push_back(face); 
						face->set_tagged(true); 
					}
					++ vfiter;
				} while (vfiter != v->vfend_safe());
				he_iter = he_iter->next ;
			}while (he_iter != he_end) ;
		}
		pseudo_mesh->construct_vertex_mapping (); 	
	}
	Brush * get_brush () {return this->mp_brush ;}
private: 
	Shader * mp_shader ;
	Brush * mp_brush ;

	face_type * center_face ;

	std::vector<face_type * > v_faces_tagged;  // for bfs culling. 
	bool m_update_whole_model ; 
	std::set<face_type * > m_selected_faces ;  // up | down sampling. 

};

	}}
#endif
